import serial
import serial.tools.list_ports
import crcmod.predefined
import logging
import struct  # 新增：用于解析float类型数据
import time
from typing import Optional, Dict, Union
import numpy as np  # 用于统计计算
import pandas as pd  # 用于创建Excel文件
from TOF_Measure import TOF_Measure

# 配置日志（AOP：日志功能与业务逻辑解耦合，便于调试和监控）
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    handlers=[logging.StreamHandler()]
)
logger = logging.getLogger("PSH2501_UART_Test")


class PSH2501_DToF(TOF_Measure):
    """PSH2501单点dToF传感器UART通信类，封装核心功能与协议解析"""
    def __init__(self, port: Optional[str] = None, baudrate: int = 921600):
        """
        初始化串口与协议参数
        :param port: 串口端口（如"COM3"或"/dev/ttyUSB0"），None则自动搜索
        :param baudrate: 波特率（默认921600bps，与模组一致）
        """
        # 调用父类初始化
        super().__init__(port, baudrate)
        
        # 串口配置
        self.ser = serial.Serial()
        self.ser.baudrate = baudrate
        self.ser.bytesize = serial.EIGHTBITS
        self.ser.stopbits = serial.STOPBITS_ONE
        self.ser.parity = serial.PARITY_NONE
        self.ser.timeout = 1  # 读取超时时间（秒）
        # 自动搜索串口（若未指定）
        if port is None:
            self.ser.port = self._auto_search_port()
        else:
            self.ser.port = port
        # 协议指令定义（十六进制字节串）
        self.commands = {
            "open_flow": b"\xA5\x09\x20\x01\x00\x00\x00\x00\x00",  # 开流指令（CMD=0x01）
            "close_flow": b"\xA5\x09\x20\x02\x00\x00\x00\x00\x00",  # 关流指令（CMD=0x02）
            "read_version": b"\xA5\x09\x20\x0A\x00\x00\x00\x00\x00"  # 读版本号指令（CMD=0x0A）
        }
        # 测量数据存储
        self.measurements = []  # 存储所有测量值
        self.max_measurements = 50  # 最大测量次数
        self.sensor_type = "PH2501"
        self.distance = 0.0  # 最新测距值（毫米）

    def open(self) -> bool:
        """
        打开传感器连接
        实现基类的抽象方法
        
        Returns:
            bool: 打开是否成功
        """
        try:
            if not self.ser.is_open:
                self.ser.open()
                self.is_connected = True
                logger.info(f"串口已打开：{self.ser.port}（波特率：{self.ser.baudrate}）")
                return True
            else:
                logger.warning(f"串口{self.ser.port}已处于打开状态")
                self.is_connected = True
                return True
        except Exception as e:
            logger.error(f"打开串口失败：{str(e)}")
            return False

    def close(self) -> bool:
        """
        关闭传感器连接
        实现基类的抽象方法
        
        Returns:
            bool: 关闭是否成功
        """
        try:
            # 先发送关流指令
            if self.is_connected and self.ser.is_open:
                close_reply = self.send_command("close_flow")
                if close_reply:
                    self.parse_close_flow_reply(close_reply)
            
            # 然后关闭串口
            if self.ser.is_open:
                self.ser.close()
                self.is_connected = False
                logger.info(f"串口{self.ser.port}已关闭")
                return True
            else:
                logger.warning(f"串口{self.ser.port}已处于关闭状态")
                return True
        except Exception as e:
            logger.error(f"关闭串口失败：{str(e)}")
            return False

    def parse_data(self, data: bytes) -> Optional[Dict[str, Union[float, int]]]:
        """
        解析传感器数据
        实现基类的抽象方法
        
        Args:
            data: 从传感器读取的原始数据
            
        Returns:
            包含distance(mm)等信息的字典，解析失败返回None
        """
        if len(data) != 56:
            logger.error("解析开流回复失败：数据长度不是56字节")
            return None
        
        try:
            # 距离值：7~11字节（4字节），小端序float，单位mm
            distance_bytes = data[7:11]
            distance = struct.unpack('<f', distance_bytes)[0]  # <f 表示小端序32位float
            # 主峰积分值：12~16字节（4字节），小端序无符号整数
            peak_integral_bytes = data[12:16]
            peak_integral = int.from_bytes(peak_integral_bytes, byteorder='little')
            # 信噪比：44~46字节（2字节），小端序无符号短整数
            snr_bytes = data[44:46]
            snr = int.from_bytes(snr_bytes, byteorder='little')
            
            # 更新最新测距值
            self.distance = round(distance, 2)
            
            # 数据格式化（保留合理小数位）
            result = {
                "distance": self.distance,  # 距离（毫米）
                "distance_mm": self.distance,  # 距离（毫米）
                "distance_m": round(distance / 1000, 4),  # 距离（米）
                "peak_integral": peak_integral,  # 主峰积分值（反映信号强度）
                "snr": snr  # 信噪比（值越高，测量越稳定）
            }
            logger.debug(f"开流回复解析结果：{result}")
            return result
        except Exception as e:
            logger.error(f"解析开流回复失败：{str(e)}")
            return None
    
    def get_distance(self) -> Optional[float]:
        """
        获取当前测距值(mm)
        重写基类方法
        
        Returns:
            float: 测距值(mm)，获取失败返回None
        """
        if not self.is_connected:
            logger.warning("传感器未连接")
            return None
            
        try:
            # 发送开流指令
            open_reply = self.send_command("open_flow")
            if open_reply:
                result = self.parse_data(open_reply)
                if result:
                    return result["distance"]
            return None
        except Exception as e:
            logger.error(f"获取距离失败：{str(e)}")
            return None
    
    def _auto_search_port(self) -> Optional[str]:
        """自动搜索可用串口（适配Windows/Linux）"""
        ports = list(serial.tools.list_ports.comports())
        if not ports:
            logger.warning("未发现可用串口")
            return None
        # 优先选择包含"USB"或"COM"的端口（常见传感器连接）
        for port in ports:
            if "ACM" in port.device or "COM" in port.device:
                logger.info(f"自动匹配串口：{port.device}（描述：{port.description}）")
                return port.device
        # 无匹配时返回第一个端口
        logger.info(f"未找到USB串口，使用第一个可用端口：{ports[0].device}")
        return ports[0].device

    def _calculate_crc16(self, data: bytes) -> bytes:
        """
        计算CRC16校验值（MODBUS标准，大端序）
        :param data: 待校验字节串（不含CRC的原始数据）
        :return: CRC16校验字节串（2字节，大端序）
        """
        # 适配新版crcmod：每次计算重新初始化校验器（替代reset()）
        crc16 = crcmod.predefined.Crc('modbus')
        crc16.update(data)
        crc_value = crc16.crcValue
        return crc_value.to_bytes(2, byteorder='big')

    def _verify_crc16(self, data: bytes) -> bool:
        """
        验证回复数据的CRC16校验
        :param data: 完整回复字节串（含CRC）
        :return: 校验结果（True=通过，False=失败）
        """
        if len(data) < 2:
            logger.error("CRC校验失败：数据长度不足2字节")
            return False
        # 分离数据与CRC（最后2字节为CRC）
        raw_data = data[:-2]
        received_crc = data[-2:]
        # 计算本地CRC并对比
        calculated_crc = self._calculate_crc16(raw_data)
        if received_crc == calculated_crc:
            logger.debug(f"CRC校验通过（接收：{received_crc.hex()}, 计算：{calculated_crc.hex()}）")
            return True
        else:
            logger.error(f"CRC校验失败（接收：{received_crc.hex()}, 计算：{calculated_crc.hex()}）")
            return False

    def open_serial(self) -> bool:
        """打开串口连接"""
        try:
            if not self.ser.is_open:
                self.ser.open()
                logger.info(f"串口已打开：{self.ser.port}（波特率：{self.ser.baudrate}）")
                return True
            else:
                logger.warning(f"串口{self.ser.port}已处于打开状态")
                return True
        except Exception as e:
            logger.error(f"打开串口失败：{str(e)}")
            return False

    def close_serial(self) -> bool:
        """关闭串口连接"""
        try:
            if self.ser.is_open:
                self.ser.close()
                logger.info(f"串口{self.ser.port}已关闭")
                return True
            else:
                logger.warning(f"串口{self.ser.port}已处于关闭状态")
                return True
        except Exception as e:
            logger.error(f"关闭串口失败：{str(e)}")
            return False

    def send_command(self, cmd_type: str) -> Optional[bytes]:
        """
        发送指令并接收回复（新增帧头校验，确保回复与指令匹配）
        :param cmd_type: 指令类型（"open_flow"/"close_flow"/"read_version"）
        :return: 回复字节串（None=发送失败或超时）
        """
        # 1. 检查指令合法性
        if cmd_type not in self.commands:
            logger.error(f"不支持的指令类型：{cmd_type}，支持类型：{list(self.commands.keys())}")
            return None
        # 2. 检查串口状态
        if not self.ser.is_open:
            logger.error("发送指令失败：串口未打开")
            return None
        
        # 3. 获取当前指令的CMD（用于后续回复帧头校验）
        cmd = self.commands[cmd_type]
        cmd_code = cmd[3:4]  # 指令第4字节（索引3）为CMD值（如0x01=开流，0x02=关流）
        
        # 4. 发送指令
        logger.debug(f"发送指令（{cmd_type}，CMD=0x{cmd_code.hex()}）：{cmd.hex()}")
        try:
            self.ser.flushInput()  # 清空接收缓存，避免干扰
            self.ser.write(cmd)
            self.ser.flush()  # 确保指令完全发送
        except Exception as e:
            logger.error(f"发送指令失败：{str(e)}")
            return None
        
        # 5. 根据指令类型读取对应长度的回复
        reply_length_map = {
            "open_flow": 56,    # 开流回复56字节
            "close_flow": 10,   # 关流回复10字节
            "read_version": 30  # 读版本号回复30字节
        }
        reply_length = reply_length_map[cmd_type]
        try:
            reply = self.ser.read(reply_length)
            # 检查回复长度
            if len(reply) != reply_length:
                logger.error(f"接收回复长度异常（期望：{reply_length}字节，实际：{len(reply)}字节）")
                return None
            # 6. 打印完整接收帧（无论帧头校验是否通过）
            logger.info(f"接收完整回复帧（{cmd_type}）：{reply.hex()}")
            
            # 7. 校验回复帧头（确保回复与当前指令匹配，避免串包）
            # 帧头格式：0xA5（包头） + 0x09（设备号） + 0x20（设备类型） + CMD（与指令一致）
            expected_header = b"\xA5\x09\x20" + cmd_code
            if len(reply) >=4 and reply[:4] != expected_header:
                logger.error(f"回复帧头异常（指令CMD=0x{cmd_code.hex()}）：实际帧头={reply[:4].hex()}，期望={expected_header.hex()}")
                # 即使帧头异常也返回回复数据，让调用方可以进一步分析
                return reply
            
            # 8. 验证CRC16
            if not self._verify_crc16(reply):
                return None
            return reply
        except Exception as e:
            logger.error(f"接收回复失败：{str(e)}")
            return None

    def parse_open_flow_reply(self, reply: bytes) -> Optional[Dict[str, Union[float, int]]]:
        """
        修复：用struct解析float类型距离数据（替代float.from_bytes）
        解析开流指令回复（提取距离、信噪比等数据）
        :param reply: 开流指令的完整回复字节串（56字节）
        :return: 解析后的数据字典（None=解析失败）
        """
        if len(reply) != 56:
            logger.error("解析开流回复失败：数据长度不是56字节")
            return None
        
        try:
            # 距离值：7~11字节（4字节），小端序float，单位mm
            distance_bytes = reply[7:11]
            distance = struct.unpack('<f', distance_bytes)[0]  # <f 表示小端序32位float
            # 主峰积分值：12~16字节（4字节），小端序无符号整数
            peak_integral_bytes = reply[12:16]
            peak_integral = int.from_bytes(peak_integral_bytes, byteorder='little')
            # 信噪比：44~46字节（2字节），小端序无符号短整数
            snr_bytes = reply[44:46]
            snr = int.from_bytes(snr_bytes, byteorder='little')
            
            # 数据格式化（保留合理小数位）
            result = {
                "distance_mm": round(distance, 2),  # 距离（毫米）
                "distance_m": round(distance / 1000, 4),  # 距离（米）
                "peak_integral": peak_integral,  # 主峰积分值（反映信号强度）
                "snr": snr  # 信噪比（值越高，测量越稳定）
            }
            logger.info(f"开流回复解析结果：{result}")
            return result
        except Exception as e:
            logger.error(f"解析开流回复失败：{str(e)}")
            return None

    def parse_close_flow_reply(self, reply: bytes) -> Optional[bool]:
        """
        解析关流指令回复（判断关流是否成功）
        :param reply: 关流指令的完整回复字节串（10字节）
        :return: 关流结果（True=成功，False=失败，None=解析失败）
        """
        if len(reply) != 10:
            logger.error("解析关流回复失败：数据长度不是10字节")
            return None
        
        try:
            # 打印关流指令的完整回复内容
            logger.info(f"关流指令完整回复：{reply.hex()}")
            
            # 数据区第1字节（索引7）为结果标识：0x00=成功，0x01=失败
            result_flag = reply[7]
            if result_flag == 0x00:
                logger.info("关流成功")
                return True
            else:
                logger.warning(f"关流失败（标识位：0x{result_flag:02X}）")
                return False
        except Exception as e:
            logger.error(f"解析关流回复失败：{str(e)}")
            return None

    def parse_version_reply(self, reply: bytes) -> Optional[str]:
        """
        解析读取版本号指令回复（提取版本字符串）
        :param reply: 读取版本号指令的完整回复字节串（30字节）
        :return: 版本号字符串（None=解析失败）
        """
        if len(reply) != 30:
            logger.error("解析版本号回复失败：数据长度不是30字节")
            return None
        
        try:
            # 数据区：7~27字节（共21字节），ASCII字符串格式
            version_bytes = reply[7:28]
            # 解码并去除空字符（避免末尾无效字节干扰）
            version = version_bytes.decode("ascii", errors="ignore").strip('\x00')
            logger.info(f"模组版本号：{version}")
            return version
        except Exception as e:
            logger.error(f"解析版本号回复失败：{str(e)}")
            return None
    
    def add_measurement(self, distance_mm: float) -> None:
        """
        添加测量值到数据集
        :param distance_mm: 测量距离值（毫米）
        """
        self.measurements.append(distance_mm)
        # 限制测量次数
        if len(self.measurements) > self.max_measurements:
            self.measurements = self.measurements[-self.max_measurements:]
    
    def calculate_statistics(self) -> Dict[str, float]:
        """
        计算测量数据的统计信息
        :return: 包含平均值、中位数、方差等统计信息的字典
        """
        if not self.measurements:
            return {}
        
        # 转换为numpy数组便于计算
        data = np.array(self.measurements)
        
        # 计算基本统计量
        mean_value = np.mean(data)
        median_value = np.median(data)
        variance_value = np.var(data)
        
        # 找出与平均值相差最大和最小的值
        diff_from_mean = np.abs(data - mean_value)
        max_diff_index = np.argmax(diff_from_mean)
        min_diff_index = np.argmin(diff_from_mean)
        
        # 计算去除最大最小值后的平均值
        sorted_data = np.sort(data)
        trimmed_data = sorted_data[1:-1]  # 去除最大最小值
        trimmed_mean = np.mean(trimmed_data) if len(trimmed_data) > 0 else mean_value
        
        return {
            "mean": mean_value,
            "median": median_value,
            "variance": variance_value,
            "max_diff_value": data[max_diff_index],
            "min_diff_value": data[min_diff_index],
            "max_diff": diff_from_mean[max_diff_index],
            "min_diff": diff_from_mean[min_diff_index],
            "trimmed_mean": trimmed_mean,
            "count": len(data)
        }
    
    def print_measurement_table(self) -> None:
        """
        打印测量数据表格，包含统计信息
        """
        if not self.measurements:
            logger.info("没有测量数据可显示")
            return
        
        # 获取统计信息
        stats = self.calculate_statistics()
        
        # 打印表头
        print("\n" + "="*80)
        print("测量数据表格".center(80))
        print("="*80)
        
        # 打印测量数据
        print(f"{'序号':<6} {'测量值(mm)':<15} {'序号':<6} {'测量值(mm)':<15} {'序号':<6} {'测量值(mm)':<15}")
        print("-"*80)
        
        # 每行显示3个数据
        for i in range(0, len(self.measurements), 3):
            row_items = []
            for j in range(3):
                if i + j < len(self.measurements):
                    row_items.extend([
                        f"{i+j+1:<6}",
                        f"{self.measurements[i+j]:<15.2f}"
                    ])
                else:
                    row_items.extend(["", ""])
            print(" ".join(row_items))
        
        # 打印统计信息
        print("-"*80)
        print("统计信息:")
        print(f"测量次数: {stats['count']}")
        print(f"平均值: {stats['mean']:.2f} mm")
        print(f"中位数: {stats['median']:.2f} mm")
        print(f"方差: {stats['variance']:.2f}")
        print(f"去除最大最小值后的平均值: {stats['trimmed_mean']:.2f} mm")
        print(f"与平均值相差最大的值: {stats['max_diff_value']:.2f} mm (相差: {stats['max_diff']:.2f})")
        print(f"与平均值相差最小的值: {stats['min_diff_value']:.2f} mm (相差: {stats['min_diff']:.2f})")
        print("="*80)
    
    def export_to_excel(self, filename: str = "measurement_data.xls") -> bool:
        """
        将测量数据和统计信息导出为Excel文件
        :param filename: 输出文件名
        :return: 导出是否成功
        """
        if not self.measurements:
            logger.error("没有测量数据可导出")
            return False
        
        try:
            # 获取统计信息
            stats = self.calculate_statistics()
            
            # 创建Excel写入器
            with pd.ExcelWriter(filename, engine='openpyxl') as writer:
                # 创建测量数据DataFrame
                measurement_data = {
                    '序号': list(range(1, len(self.measurements) + 1)),
                    '测量值(mm)': self.measurements
                }
                df_measurements = pd.DataFrame(measurement_data)
                
                # 写入测量数据到第一个工作表
                df_measurements.to_excel(writer, sheet_name='测量数据', index=False)
                
                # 创建统计信息DataFrame
                stats_data = {
                    '统计项': ['测量次数', '平均值(mm)', '中位数(mm)', '方差', 
                              '去除最大最小值后的平均值(mm)', '与平均值相差最大的值(mm)', 
                              '与平均值相差最大的差值(mm)', '与平均值相差最小的值(mm)', 
                              '与平均值相差最小的差值(mm)'],
                    '值': [
                        stats['count'],
                        stats['mean'],
                        stats['median'],
                        stats['variance'],
                        stats['trimmed_mean'],
                        stats['max_diff_value'],
                        stats['max_diff'],
                        stats['min_diff_value'],
                        stats['min_diff']
                    ]
                }
                df_stats = pd.DataFrame(stats_data)
                
                # 写入统计信息到第二个工作表
                df_stats.to_excel(writer, sheet_name='统计信息', index=False)
                
                # 创建排序后的数据DataFrame
                sorted_measurements = sorted(self.measurements)
                sorted_data = {
                    '序号': list(range(1, len(sorted_measurements) + 1)),
                    '排序后测量值(mm)': sorted_measurements
                }
                df_sorted = pd.DataFrame(sorted_data)
                
                # 写入排序后的数据到第三个工作表
                df_sorted.to_excel(writer, sheet_name='排序数据', index=False)
            
            logger.info(f"数据已成功导出到Excel文件: {filename}")
            return True
        except Exception as e:
            logger.error(f"导出Excel文件失败: {str(e)}")
            return False


# 测试示例（业务逻辑与测试代码分离，便于复用）
if __name__ == "__main__":
    # 初始化模组对象（自动搜索串口，波特率921600）
    psh2501 = PSH2501_DToF()
    
    try:
        # 1. 打开串口连接
        if not psh2501.open_serial():
            raise Exception("串口初始化失败，无法继续测试")
        
        # 2. 读取模组版本号（验证通信基础连通性）
        logger.info("\n=== 步骤1：执行读取版本号指令 ===")
        version_reply = psh2501.send_command("read_version")
        if version_reply:
            psh2501.parse_version_reply(version_reply)
        
        # 3. 执行开流指令（开始持续测距）
        logger.info("\n=== 步骤2：执行开流指令（开始持续测距） ===")
        open_reply = psh2501.send_command("open_flow")
        if open_reply:
            psh2501.parse_open_flow_reply(open_reply)
        
        # 4. 持续测距模式
        logger.info("\n=== 步骤3：进入持续测距模式 ===")
        logger.info("按 Ctrl+C 停止测距并执行关流指令")
        logger.info("正在持续测量距离...")
        logger.info(f"将进行{psh2501.max_measurements}次测量并计算统计信息")
        
        # 持续读取开流数据
        while True:
            try:
                # 读取开流数据（56字节）
                data = psh2501.ser.read(56)
                if len(data) == 56:
                    # 解析并显示距离数据
                    result = psh2501.parse_open_flow_reply(data)
                    if result:
                        # 添加测量值到数据集
                        psh2501.add_measurement(result['distance_mm'])
                        print(f"测量#{len(psh2501.measurements)}: 距离: {result['distance_mm']}mm, 信噪比: {result['snr']}, 主峰积分: {result['peak_integral']}")
                        
                        # 检查是否达到最大测量次数
                        if len(psh2501.measurements) >= psh2501.max_measurements:
                            logger.info(f"已完成{psh2501.max_measurements}次测量，停止测距...")
                            break
                else:
                    logger.warning(f"接收数据长度异常：期望56字节，实际{len(data)}字节")
            except KeyboardInterrupt:
                logger.info("\n检测到 Ctrl+C，停止测距...")
                break
            except Exception as e:
                logger.error(f"读取数据异常：{str(e)}")
        
        # 打印测量数据表格和统计信息
        psh2501.print_measurement_table()
        
        # 导出数据到Excel文件
        timestamp = time.strftime("%Y%m%d_%H%M%S")
        excel_filename = f"measurement_data_{timestamp}.xls"
        if psh2501.export_to_excel(excel_filename):
            logger.info(f"测量数据已导出到Excel文件: {excel_filename}")
        else:
            logger.error("导出Excel文件失败")
        
        # 5. 执行关流指令（停止测量，降低功耗）
        logger.info("\n=== 步骤4：执行关流指令 ===")
        
        # 关流指令重试机制
        max_retries = 3
        close_success = False
        
        for attempt in range(max_retries):
            logger.info(f"第{attempt + 1}次尝试关流...")
            close_reply = psh2501.send_command("close_flow")
            if close_reply:
                result = psh2501.parse_close_flow_reply(close_reply)
                if result is True:
                    close_success = True
                    logger.info("关流成功")
                    break
                else:
                    logger.warning(f"第{attempt + 1}次关流失败，等待1秒后重试...")
                    time.sleep(1)
            else:
                logger.warning(f"第{attempt + 1}次关流指令发送失败，等待1秒后重试...")
                time.sleep(1)
        
        if not close_success:
            logger.error(f"关流失败，已重试{max_retries}次")
    
    except KeyboardInterrupt:
        logger.info("\n检测到 Ctrl+C，程序被用户中断")
    except Exception as e:
        logger.error(f"测试过程异常终止：{str(e)}")
    
    finally:
        # 确保无论测试结果如何，最终关闭串口
        psh2501.close_serial()
        logger.info("\n=== 测试流程结束 ===")